package com.ikingtech.framework.sdk.log.embedded.aspect;

import com.fasterxml.jackson.core.type.TypeReference;
import com.ikingtech.framework.sdk.context.security.Identity;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.enums.common.FrameworkAgentTypeEnum;
import com.ikingtech.framework.sdk.enums.log.SignTypeEnum;
import com.ikingtech.framework.sdk.log.embedded.annotation.AuthLog;
import com.ikingtech.framework.sdk.log.model.rpc.AuthLogReportParam;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.support.agent.FrameworkAgentProxy;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.MessageSource;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.LocalDateTime;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 登录日志处理
 *
 * @author zhangqiang
 */
@Aspect
@Slf4j
@RequiredArgsConstructor
public class AuthLogAspect {

    private final MessageSource messageSource;

    private ThreadPoolExecutor threadPool;

    public void init() {
        threadPool = new ThreadPoolExecutor(
                2,
                100,
                30L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3000),
                r -> new Thread(r, "auth-log-ThreadPool-" + r.hashCode()),
                (r, executor) -> r.run());
    }

    @AfterReturning(pointcut = "@annotation(authLog)", returning = "result")
    public void doAfterReturning(AuthLog authLog, Object result) {
        AuthLogReportParam log = parseReturning(authLog, result);
        // 保存数据库
        threadPool.execute(() -> this.send(log));
    }

    @AfterThrowing(pointcut = "@annotation(authLog)", throwing = "e")
    public void doAfterThrowing(AuthLog authLog, Exception e) {
        AuthLogReportParam logReportParam = this.parseThrowing(authLog, e);
        // 保存数据库
        threadPool.execute(() -> this.send(logReportParam));
    }

    private AuthLogReportParam parseThrowing(AuthLog authLog, Exception e) {
        AuthLogReportParam logReportParam = this.parseReturning(authLog, null);
        logReportParam.setSuccess(false);
        logReportParam.setMessage(e.getClass() + System.lineSeparator() + (null == e.getCause() ? "" : e.getCause().getMessage()) + System.lineSeparator() + e.getMessage());
        return logReportParam;
    }

    private AuthLogReportParam parseReturning(AuthLog authLog, Object proceedResult) {
        AuthLogReportParam log = new AuthLogReportParam();
        // 设置详细结果
        if (null != proceedResult) {
            R<Identity> result = Tools.Json.objToBean(proceedResult, new TypeReference<>() {
            });
            if (SignTypeEnum.SIGN_IN.equals(authLog.type()) && null != result.getData()) {
                log.setSuccess(result.getData().getAuthenticated());
                log.setUserId(result.getData().getId());
                log.setUsername(result.getData().getUsername());
            } else {
                log.setSuccess(result.isSuccess());
            }
            log.setMessage(result.getMsg());
        } else {
            log.setSuccess(true);
            log.setMessage(this.messageSource.getMessage("methodDidNotReturnResult", null, Locale.forLanguageTag(Me.lang())));
        }
        log.setType(authLog.type());
        log.setTypeName(authLog.type().description);
        HttpServletRequest request = Objects.requireNonNull(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())).getRequest();
        String ip = Tools.Network.ip(request);
        log.setIp(ip);
        log.setLocation(Tools.Network.ipRegion(ip));
        Tools.Network.UserAgentInfo userAgent = Tools.Network.userAgent(request.getHeader("User-Agent"));
        log.setBrowser(userAgent.getBrowser());
        log.setOs(userAgent.getOperatingSystem());
        log.setSignTime(LocalDateTime.now());
        return log;
    }

    private void send(AuthLogReportParam logDTO) {
        FrameworkAgentProxy.agent().execute(FrameworkAgentTypeEnum.AUTH_LOG, logDTO);
    }
}
